/***************************************************************
*  Copyright (c) 2007 by GroupMe! Team (www.groupme.net)
*  All rights reserved.
*
*  This file is part of the GroupMe! Project. Source code of 
*  this project is closed and redistribution of this code is
*  prohibited. 
*  
*  Contact: http://www.groupme.net
*
*  This copyright notice MUST APPEAR in all copies of the file!
***************************************************************/
package net.groupme.controller;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.groupme.api.API_CONSTANTS;
import net.groupme.gui.constants.VIEW_VOCABULARY;
import net.groupme.model.ObjectFactory;
import net.groupme.model.Resource;
import net.groupme.model.Tag;
import net.groupme.search.GroupMeSearchEngine;
import net.groupme.search.SearchManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

/**
 * created on May 7, 2008
 * 
 * This controller handles API calls. The API is called
 * by third-party applications and allows them to search
 * within the GroupMe! system, etc.<br/>
 * 
 * This controller contains already the logic for parsing
 * an API request and computing the results. 
 * 
 * @author Fabian Abel, <a href="mailto:abel@l3s.de">abel@l3s.de</a>
 * @author last edited by: $Author: fabian $
 * 
 * @version $Revision: 1.1 $ $Date: 2008-05-07 18:17:57 $
 */
public class APIController implements Controller, ApplicationContextAware {

	/** Logger for this class and subclasses */
	protected final Log logger = LogFactory.getLog(getClass());
	
	
	/** the application context */
	private ApplicationContext applicationContext = null;

	/**
	 * Set the ApplicationContext that this object runs in. 
	 * @param applicationContext the current {@link ApplicationContext} 
	 */
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;		
	}
	
	/** GroupMe! search engine */
    private GroupMeSearchEngine searchEngine = null;
	
	/* (non-Javadoc)
	 * @see org.springframework.web.servlet.mvc.Controller#handleRequest(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// init search engine if not done so far:
		if(searchEngine == null){
			searchEngine = (GroupMeSearchEngine) WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext()).getBean(VIEW_VOCABULARY.GROUPME_SEARCH_ENGINE, GroupMeSearchEngine.class);	
		}
		
		//create the MaV:
    	ModelAndView mav = new ModelAndView();
    	
    	/*
    	 * extract parameter:
    	 */
    	//method:
    	String method = getStringValue(request, API_CONSTANTS.PARAMETER_METHOD);
    	//output:
    	String output = getStringValue(request, API_CONSTANTS.PARAMETER_OUTPUT);
    	if(output == null){//Standard output format is XML:
    		output = API_CONSTANTS.OUTPUT_XML;
    	}
    	//tags:
    	String query = null;//will be passed to the client within the result
    	List<Tag> tags = new ArrayList<Tag>();
    	String[] tagArray = request.getParameterValues(API_CONSTANTS.PARAMETER_TAG);
    	for (int i = 0; i < tagArray.length; i++) {
			tags.add(ObjectFactory.getTag(tagArray[i]));
			if(query == null){
				query = tagArray[i];
			}else{
				query += ", " + tagArray[i];
			}
		}
    	//tag conjunction:
    	boolean andConnected = false; //standard is: false
    	if(API_CONSTANTS.TAG_CONJUNCTION_AND.equals(getStringValue(request, API_CONSTANTS.PARAMETER_TAG_CONJUNCTION))){
    		andConnected = true;
    	}
    
    	/*
    	 * Some error checking (Are all relevant parameters set correctly?)
    	 */
    	if(!checkParameters(method, tags)){
    		setOutputFormatSpecificErrorView(mav, output);
    		return mav;
    	}
    	
    	
    	
    	/*
    	 * CALL METHOD:
    	 */
    	if(API_CONSTANTS.ALL_SEARCH_METHODS.contains(method)){
    		List<Resource> result = new ArrayList<Resource>();
    		if(API_CONSTANTS.METHOD_SEARCH_FOR_GROUPS_BY_TAG.equals(method)){
    			result.addAll(searchEngine.searchForGroups(tags, andConnected));
        	}else if(API_CONSTANTS.METHOD_SEARCH_FOR_RESOURCE_BY_TAG.equals(method)){
    			result.addAll(searchEngine.searchForResources(tags, andConnected, true));
        	}else if(API_CONSTANTS.METHOD_SEARCH_FOR_RESOURCES_AND_GROUPS_BY_TAG.equals(method)){
    			result.addAll(searchEngine.searchForResources(tags, andConnected, false));
        	}
    		//extract parameter:
    		
        	//offset:
        	String offsetString = getStringValue(request, API_CONSTANTS.PARAMETER_OFFSET);
        	int offset = 0;
        	if(offsetString != null){
        		try{
        			offset = Integer.parseInt(offsetString);
        			if(offset >= result.size()){
        				offset = result.size() -1; //we return the last resource of the ranking so that the client recognizes that the offset was chosen too high..
        			}
        			if(offset < 0){
        				offset = 0;
        			}
        		}catch(NumberFormatException e){// just ignore the offset
        			logger.info("Given offset cannot be parsed. Offset will be ignored.");
        		} 
        	}
     
        	//limit:
        	String limitString = getStringValue(request, API_CONSTANTS.PARAMETER_LIMIT);
        	int limit = result.size() - offset;
        	if(limitString != null){
        		try{
        			limit = Integer.parseInt(limitString);
        			if(limit >= result.size() - offset){
        				limit = result.size() - offset; 
        			}else if(limit < 1){
        				limit = 1;
        			}
        		}catch(NumberFormatException e){// just ignore the limit
        			logger.info("Given limit cannot be parsed. Limit will be ignored.");
        		} 
        	}
        	
        	logger.info("Query: " + query + (andConnected ? " (AND" : " (OR") + "-connected) - Number of results (without limit/offset): " + result.size() );
        	mav.addObject(API_CONSTANTS.OBJECT_OFFSET, new Integer(offset));
        	mav.addObject(API_CONSTANTS.OBJECT_RESULT_LIST, result.subList(offset, offset + limit));
        	mav.addObject(API_CONSTANTS.OBJECT_QUERY, query);
        	mav.setViewName(API_CONSTANTS.VIEW_XML_SEARCHRESULT);
    	}
    	
    	
		return mav;
	}

	
	
	/**
	 * Checks if all relevant parameters set correctly and sets the view in case of invalid
	 * parameters.
	 *  
	 * @param method the method value (parameter: {@link API_CONSTANTS#PARAMETER_METHOD})
	 * @param tags the list of tags in case of a search method (parameter: {@link API_CONSTANTS#PARAMETER_TAG})
	 * @return <code>true</code> if all parameters are valid
	 */
	private boolean checkParameters(String method, List<Tag> tags){
		if(method == null || !API_CONSTANTS.ALL_METHODS.contains(method)){
			return false;
		}else if(API_CONSTANTS.ALL_SEARCH_METHODS.contains(method) && (tags == null || tags.isEmpty())){
			return false;
		}
		
		return true;
	}
	/**
	 * Set View to the output format specific
	 * error-view (standard output format is {@link API_CONSTANTS#OUTPUT_XML}.
	 * @param mav MaV that will be set to the output format specific
	 * error-view (standard output format is {@link API_CONSTANTS#OUTPUT_XML}.
	 * @param outputFormat the output format value (parameter: {@link API_CONSTANTS#PARAMETER_OUTPUT})
	 */
	private void setOutputFormatSpecificErrorView(ModelAndView mav, String outputFormat){
		if(API_CONSTANTS.OUTPUT_XML.equals(outputFormat)){
			mav.setViewName(API_CONSTANTS.VIEW_XML_ERROR);
		}
		//to be completed..
		else{
			mav.setViewName(API_CONSTANTS.VIEW_XML_ERROR);
		}
	}
	
	/**
     * Very small helper function to extract a given parameter or attribute from a HttpServletRequest.
     * @param request the {@link HttpServletRequest} object
     * @param parameter the parameter
     * @return the value of the parameter as a String
     */
    private String getStringValue(HttpServletRequest request, String parameter){
    	String value = request.getParameter(parameter);
    	if(value == null && request.getAttribute(parameter) != null){ //normally this is not accessed but we do our best to extract the value.. 
    		value = request.getAttribute(parameter).toString();
    	}
    	return value;
    }
}
